import os
import yaml
from functools import wraps, reduce
from pathlib import Path
import difflib
from typing import Any

# 合法配置白名单结构
available_setting = {
    "name": "",
    "version": "",
    "logging": {
        "level": ["debug", "info", "warning", "error", "critical"]
    },
    "WakeWord": "",
    "interrupt": False,
    "StartTaskMo de": True,
    "selected_module": {
        "Recorder": ["RecorderPyAudio", "OtherRecorder"],
        "ASR": ["FunASR", "AzureASR", "Whisper"],
        "VAD": ["SileroVAD", "WebRTCVAD"],
        "LLM": ["OpenAILLM", "LocalLLM", "ClaudeLLM"],
        "TTS": ["EdgeTTS", "CoquiTTS", "AzureTTS"],
        "Player": ["PygameSoundPlayer", "SoundDevicePlayer"]
    },
    "Recorder": {
        "RecorderPyAudio": {
            "output_file": "/tmp/",
            "__validate__": lambda cfg: (
                    Path(cfg["output_file"]).is_dir() or
                    "output_file 必须为有效目录"
            )
        }
    }
}


def get_project_path():
    # 获取当前脚本的目录
    current_dir = os.path.dirname(os.path.abspath(__file__))
    # 假设项目根目录是当前目录的上级目录
    return os.path.dirname(current_dir)


class Config(dict):
    def __init__(self, yaml_path: str = "config/config.yaml"):
        super().__init__()
        # 加载YAML配置
        with open(self.get_config_file_path(yaml_path)) as f:
            raw_config = yaml.safe_load(f)
        self._load_nested_config(raw_config)
        # 执行后置校验
        self._post_validation()

    def _load_nested_config(self, config_data: dict, parent_key: str = ""):
        """递归加载嵌套配置"""
        for key, value in config_data.items():
            full_key = f"{parent_key}.{key}" if parent_key else key
            if isinstance(value, dict):
                self[full_key] = {}
                self._load_nested_config(value, full_key)
            else:
                self[full_key] = value

    def _validate_key_path(self, key_path: str):
        """递归验证配置路径合法性"""
        current_level = available_setting
        keys = key_path.split('.')

        for idx, key in enumerate(keys):
            if not isinstance(current_level, dict) or key not in current_level:
                similar = difflib.get_close_matches(
                    key,
                    current_level.keys() if isinstance(current_level, dict) else [],
                    n=3
                )
                hint = f"，建议使用：{similar}" if similar else ""
                raise KeyError(f"非法配置项 '{key}' (路径: {'/'.join(keys[:idx])}){hint}")

            current_level = current_level[key]

            # 最后一层检查枚举值（使用父类方法获取实际值）
            if idx == len(keys) - 1 and isinstance(current_level, list):
                actual_value = super(Config, self).get(key_path)
                if actual_value not in current_level:
                    raise ValueError(
                        f"配置项 {key_path} 的值 '{actual_value}' 无效，"
                        f"允许值：{current_level}"
                    )

    @staticmethod
    def get_config_file_path(file_name: str) -> str:
        """
        获取配置文件的完整路径
        """
        common_dir = os.path.dirname(os.path.abspath(__file__))
        project_root = os.path.dirname(common_dir)
        return os.path.join(project_root, file_name)

    @staticmethod
    def _get_expected_type(key_path: str) -> type:
        """通过白名单获取期望的类型"""
        current = available_setting
        for key in key_path.split('.'):
            if key not in current:
                return type(None)
            current = current[key]
        return type(current) if not isinstance(current, list) else str

    def __getitem__(self, key: str) -> Any:
        def _get_nested(d, keys):
            """直接操作字典结构的嵌套查询"""
            if len(keys) == 1:
                return super(Config, self).get(keys[0])
            return _get_nested(d.get(keys[0], {}), keys[1:])

        # 点分语法处理
        if '.' in key:
            self._validate_key_path(key)
            return _get_nested(self, key.split('.'))

        # 单层处理
        if key not in available_setting:
            similar = difflib.get_close_matches(key, available_setting.keys(), n=3)
            hint = f"，您可能想输入的是：{similar}" if similar else ""
            raise KeyError(f"非法配置项 '{key}'{hint}")

        return super().get(key, available_setting[key])

    def __setitem__(self, key: str, value: Any):
        # 类型检查
        expected_type = self._get_expected_type(key)
        if not isinstance(value, expected_type) and expected_type != type(None):
            raise TypeError(
                f"配置项 '{key}' 类型错误，应为 {expected_type}，实际为 {type(value)}"
            )

        # 路径合法性检查
        if '.' in key:
            self._validate_key_path(key)
            keys = key.split('.')
            current = self
            for k in keys[:-1]:
                current = current.setdefault(k, {})
            current[keys[-1]] = value
        else:
            if key not in available_setting:
                similar = difflib.get_close_matches(key, available_setting.keys(), n=3)
                hint = f"，您可能想输入的是：{similar}" if similar else ""
                raise KeyError(f"非法配置项 '{key}'{hint}")
            super().__setitem__(key, value)

    def _post_validation(self):
        """执行自定义校验规则"""
        if 'Recorder.RecorderPyAudio' in self:
            # 使用直接字典操作获取配置
            recorder_cfg = super(Config, self).get('Recorder.RecorderPyAudio', {})
            validate_func = available_setting['Recorder']['RecorderPyAudio'].get('__validate__')
            if validate_func and recorder_cfg:
                result = validate_func(recorder_cfg)
                if isinstance(result, str):
                    raise ValueError(f"RecorderPyAudio 配置错误: {result}")

    def get(self, key: str, default: Any = None) -> Any:
        try:
            return self[key]
        except KeyError:
            return default

    def to_dict(self) -> dict:
        """转换为嵌套字典结构（使用父类方法避免递归）"""
        result = {}
        for key in self.available_keys():
            keys = key.split('.')
            current = result
            for k in keys[:-1]:
                current = current.setdefault(k, {})
            # 使用父类方法获取值
            current[keys[-1]] = super(Config, self).get(key)
        return result

    def available_keys(self) -> list:
        """生成所有合法配置路径"""

        def _walk(d, prefix=""):
            for k, v in d.items():
                if k.startswith("__"):
                    continue
                if isinstance(v, dict) and not isinstance(v, list):
                    yield from _walk(v, f"{prefix}{k}.")
                else:
                    yield f"{prefix}{k}"

        return list(_walk(available_setting))


# 使用示例
if __name__ == "__main__":
    try:
        config = Config()

        # 合法访问示例
        print("当前version:", config["version"])

        # 非法操作示例测试
        # config["selected_module.ASR"] = "InvalidASR"  # 触发值错误
        # config["nonexistent.key"] = "value"         # 触发路径错误
        # config["logging.level"] = 123               # 触发类型错误

    except (KeyError, ValueError, TypeError) as e:
        print(f"配置错误: {e}")